/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */
#include "SinbadCharacter.h"

namespace OgreCamNav
{
	SinbadCharacter::SinbadCharacter(Ogre::Camera *camera)
	{
		setupBody(camera->getSceneManager());
		setupCamera(camera);
		setupAnimations();
		setupParticle(camera->getSceneManager());
		keepOnGround();
	}

	//for rain and other natural effect
	void SinbadCharacter::setupParticle(Ogre::SceneManager *sceneMgr)
	{
		// Create a rainstorm
		Ogre::ParticleSystem *rainSystem=sceneMgr->createParticleSystem("rain","Natural/Rain");

		Ogre::SceneNode* node = bodyNode->createChildSceneNode();
        node->translate(0,100,0);
        node->attachObject(rainSystem);
        // Fast-forward the rain so it looks more natural
        rainSystem->fastForward(5);

		Ogre::ParticleSystem::setDefaultNonVisibleUpdateTimeout(5);

	}

	void SinbadCharacter::keepOnGround()
	{
		rayQuery->setRay(Ogre::Ray(bodyNode->getPosition()+Ogre::Vector3(0,-2000,0),Ogre::Vector3::UNIT_Y));
		Ogre::RaySceneQueryResult &result=rayQuery->execute();
		Ogre::RaySceneQueryResult::iterator i=result.begin();
		if(i!=result.end()&&i->worldFragment){
			bodyNode->setPosition(bodyNode->getPosition().x,i->worldFragment->singleIntersection.y+OFF_GROUND+CHAR_HEIGHT,bodyNode->getPosition().z);
		}
	}

	void SinbadCharacter::setupBody(Ogre::SceneManager *sceneMgr)
	{
		// create main model
		bodyNode = sceneMgr->getRootSceneNode()->createChildSceneNode(Ogre::Vector3::UNIT_Y * CHAR_HEIGHT);
		bodyEntity = sceneMgr->createEntity("SinbadBody", "Sinbad.mesh");
		bodyNode->attachObject(bodyEntity);

		// create swords and attach to sheath
		sword1 = sceneMgr->createEntity("SinbadSword1", "Sword.mesh");
		sword2 = sceneMgr->createEntity("SinbadSword2", "Sword.mesh");
		bodyEntity->attachObjectToBone("Sheath.L", sword1);
		bodyEntity->attachObjectToBone("Sheath.R", sword2);

		// create a couple of ribbon trails for the swords, just for fun
		Ogre::NameValuePairList params;
		params["numberOfChains"] = "2";
		params["maxElements"] = "80";
		swordTrail = (Ogre::RibbonTrail*)sceneMgr->createMovableObject("swordTrail","RibbonTrail", &params);
		swordTrail->setMaterialName("Examples/LightRibbonTrail");
		swordTrail->setTrailLength(20);
		swordTrail->setVisible(false);
		sceneMgr->getRootSceneNode()->attachObject(swordTrail);

		for (int i = 0; i < 2; i++){
			swordTrail->setInitialColour(i, 1, 0.8, 0);
			swordTrail->setColourChange(i, 0.75, 1.25, 1.25, 1.25);
			swordTrail->setWidthChange(i, 1);
			swordTrail->setInitialWidth(i, 0.5);
		}

		keyDirection = Ogre::Vector3::ZERO;
		verticalVelocity = 0;

		rayQuery=sceneMgr->createRayQuery(Ogre::Ray(bodyNode->getPosition(),Ogre::Vector3::NEGATIVE_UNIT_Y));
	}

	void SinbadCharacter::setupCamera(Ogre::Camera *cam)
	{
		camera=cam;
		// create a pivot at roughly the character's shoulder
		cameraPivot = cam->getSceneManager()->getRootSceneNode()->createChildSceneNode();
		// this is where the camera should be soon, and it spins around the pivot
		cameraGoal = cameraPivot->createChildSceneNode(Ogre::Vector3(0, 0, 15));
		// this is where the camera actually is
		cameraNode = cam->getSceneManager()->getRootSceneNode()->createChildSceneNode();

		cameraNode->setPosition(cameraPivot->getPosition() + cameraGoal->getPosition());

		cameraPivot->setFixedYawAxis(true);
		cameraGoal->setFixedYawAxis(true);
		cameraNode->setFixedYawAxis(true);

		cameraNode->attachObject(cam);

		cam->setNearClipDistance(1.0f);
		cam->setFarClipDistance(2000.0f);

		pivotPitch = 0;
	}

	void SinbadCharacter::setupAnimations()
	{
		// this is very important due to the nature of the exported animations
		bodyEntity->getSkeleton()->setBlendMode(Ogre::ANIMBLEND_CUMULATIVE);

		Ogre::String animNames[] =
		{"IdleBase", "IdleTop", "RunBase", "RunTop", "HandsClosed", "HandsRelaxed", "DrawSwords",
		"SliceVertical", "SliceHorizontal", "Dance", "JumpStart", "JumpLoop", "JumpEnd"};

		// populate our animation list
		for (int i = 0; i < NUM_ANIMS; i++){
			anims[i] = bodyEntity->getAnimationState(animNames[i]);
			anims[i]->setLoop(true);
			fadingIn[i] = false;
			fadingOut[i] = false;
		}

		// start off in the idle state (top and bottom together)
		setBaseAnimation(ANIM_IDLE_BASE);
		setTopAnimation(ANIM_IDLE_TOP);

		// relax the hands since we're not holding anything
		anims[ANIM_HANDS_RELAXED]->setEnabled(true);

		swordsDrawn = false;
	}

	void SinbadCharacter::setBaseAnimation(AnimID id, bool reset)
	{
		if (baseAnimID >= 0 && baseAnimID < NUM_ANIMS){
			// if we have an old animation, fade it out
			fadingIn[baseAnimID] = false;
			fadingOut[baseAnimID] = true;
		}

		baseAnimID = id;

		if (id != ANIM_NONE){
			// if we have a new animation, enable it and fade it in
			anims[id]->setEnabled(true);
			anims[id]->setWeight(0);
			fadingOut[id] = false;
			fadingIn[id] = true;
			if (reset) anims[id]->setTimePosition(0);
		}
	}

	void SinbadCharacter::setTopAnimation(AnimID id, bool reset)
	{
		if (topAnimID >= 0 && topAnimID < NUM_ANIMS){
			// if we have an old animation, fade it out
			fadingIn[topAnimID] = false;
			fadingOut[topAnimID] = true;
		}

		topAnimID = id;

		if (id != ANIM_NONE){
			// if we have a new animation, enable it and fade it in
			anims[id]->setEnabled(true);
			anims[id]->setWeight(0);
			fadingOut[id] = false;
			fadingIn[id] = true;
			if (reset) anims[id]->setTimePosition(0);
		}
	}

	void SinbadCharacter::update(Ogre::Real deltaTime)
	{
		updateBody(deltaTime);
		updateAnimations(deltaTime);
		updateCamera(deltaTime);
	}

	void SinbadCharacter::updateBody(Ogre::Real deltaTime)
	{
		goalDirection = Ogre::Vector3::ZERO;   // we will calculate this

		if (keyDirection != Ogre::Vector3::ZERO && baseAnimID != ANIM_DANCE){
			// calculate actually goal direction in world based on player's key directions
			goalDirection += keyDirection.z * cameraNode->getOrientation().zAxis();
			goalDirection += keyDirection.x * cameraNode->getOrientation().xAxis();
			goalDirection.y = 0;
			goalDirection.normalise();

			Ogre::Quaternion toGoal = bodyNode->getOrientation().zAxis().getRotationTo(goalDirection);

			// calculate how much the character has to turn to face goal direction
			Ogre::Real yawToGoal = toGoal.getYaw().valueDegrees();
			// this is how much the character CAN turn this frame
			Ogre::Real yawAtSpeed = yawToGoal / Ogre::Math::Abs(yawToGoal) * deltaTime * TURN_SPEED;
			// reduce "turnability" if we're in midair
			if (baseAnimID == ANIM_JUMP_LOOP) yawAtSpeed *= 0.2f;

			// turn as much as we can, but not more than we need to
			if (yawToGoal < 0) yawToGoal = std::min<Ogre::Real>(0, std::max<Ogre::Real>(yawToGoal, yawAtSpeed)); //yawToGoal = Math::Clamp<Real>(yawToGoal, yawAtSpeed, 0);
			else if (yawToGoal > 0) yawToGoal = std::max<Ogre::Real>(0, std::min<Ogre::Real>(yawToGoal, yawAtSpeed)); //yawToGoal = Math::Clamp<Real>(yawToGoal, 0, yawAtSpeed);
			
			bodyNode->yaw(Ogre::Degree(yawToGoal));

			// move in current body direction (not the goal direction)
			bodyNode->translate(0, 0, deltaTime * RUN_SPEED * anims[baseAnimID]->getWeight(),
				Ogre::Node::TS_LOCAL);
		}

		if (baseAnimID == ANIM_JUMP_LOOP){
			// if we're jumping, add a vertical offset too, and apply gravity
			bodyNode->translate(0, verticalVelocity * deltaTime, 0, Ogre::Node::TS_LOCAL);
			verticalVelocity -= GRAVITY * deltaTime;
			
			rayQuery->setRay(Ogre::Ray(bodyNode->getPosition()+Ogre::Vector3(0,-2000,0),Ogre::Vector3::UNIT_Y));
			Ogre::RaySceneQueryResult &result=rayQuery->execute();
			Ogre::RaySceneQueryResult::iterator i=result.begin();
			if(i!=result.end()&&i->worldFragment){
				if(i->worldFragment->singleIntersection.distance(bodyNode->getPosition())<=CHAR_HEIGHT){
					// if we've hit the ground, change to landing state
					bodyNode->setPosition(bodyNode->getPosition().x,i->worldFragment->singleIntersection.y+OFF_GROUND+CHAR_HEIGHT,bodyNode->getPosition().z);
					
					setBaseAnimation(ANIM_JUMP_END, true);
					timer = 0;
				}

			}
		}else{
			keepOnGround();
		}
	}

	void SinbadCharacter::updateAnimations(Ogre::Real deltaTime)
	{
		Ogre::Real baseAnimSpeed = 1;
		Ogre::Real topAnimSpeed = 1;

		timer += deltaTime;

		if (topAnimID == ANIM_DRAW_SWORDS){
			// flip the draw swords animation if we need to put it back
			topAnimSpeed = swordsDrawn ? -1 : 1;

			// half-way through the animation is when the hand grasps the handles...
			if (timer >= anims[topAnimID]->getLength() / 2 &&
				timer - deltaTime < anims[topAnimID]->getLength() / 2){
				// so transfer the swords from the sheaths to the hands
				bodyEntity->detachAllObjectsFromBone();
				bodyEntity->attachObjectToBone(swordsDrawn ? "Sheath.L" : "Handle.L", sword1);
				bodyEntity->attachObjectToBone(swordsDrawn ? "Sheath.R" : "Handle.R", sword2);
				// change the hand state to grab or let go
				anims[ANIM_HANDS_CLOSED]->setEnabled(!swordsDrawn);
				anims[ANIM_HANDS_RELAXED]->setEnabled(swordsDrawn);

				// toggle sword trails
				if (swordsDrawn){
					swordTrail->setVisible(false);
					swordTrail->removeNode(sword1->getParentNode());
					swordTrail->removeNode(sword2->getParentNode());
				}else{
					swordTrail->setVisible(true);
					swordTrail->addNode(sword1->getParentNode());
					swordTrail->addNode(sword2->getParentNode());
				}
			}

			if (timer >= anims[topAnimID]->getLength()){
				// animation is finished, so return to what we were doing before
				if (baseAnimID == ANIM_IDLE_BASE) {
					setTopAnimation(ANIM_IDLE_TOP);
				}else{
					setTopAnimation(ANIM_RUN_TOP);
					anims[ANIM_RUN_TOP]->setTimePosition(anims[ANIM_RUN_BASE]->getTimePosition());
				}
				swordsDrawn = !swordsDrawn;
			}
		}else if(topAnimID == ANIM_SLICE_VERTICAL || topAnimID == ANIM_SLICE_HORIZONTAL){
			if (timer >= anims[topAnimID]->getLength()){
				// animation is finished, so return to what we were doing before
				if (baseAnimID == ANIM_IDLE_BASE) {
					setTopAnimation(ANIM_IDLE_TOP);
				}else{
					setTopAnimation(ANIM_RUN_TOP);
					anims[ANIM_RUN_TOP]->setTimePosition(anims[ANIM_RUN_BASE]->getTimePosition());
				}
			}

			// don't sway hips from side to side when slicing. that's just embarrasing.
			if (baseAnimID == ANIM_IDLE_BASE) baseAnimSpeed = 0;
		}else if (baseAnimID == ANIM_JUMP_START){
			if (timer >= anims[baseAnimID]->getLength()){
				// takeoff animation finished, so time to leave the ground!
				setBaseAnimation(ANIM_JUMP_LOOP, true);
				// apply a jump acceleration to the character
				verticalVelocity = JUMP_ACCEL;
			}
		}else if (baseAnimID == ANIM_JUMP_END){
			if (timer >= anims[baseAnimID]->getLength()){
				// safely landed, so go back to running or idling
				if (keyDirection == Ogre::Vector3::ZERO){
					setBaseAnimation(ANIM_IDLE_BASE);
					setTopAnimation(ANIM_IDLE_TOP);
				}else{
					setBaseAnimation(ANIM_RUN_BASE, true);
					setTopAnimation(ANIM_RUN_TOP, true);
				}
			}
		}

		// increment the current base and top animation times
		if (baseAnimID != ANIM_NONE) anims[baseAnimID]->addTime(deltaTime * baseAnimSpeed);
		if (topAnimID != ANIM_NONE) anims[topAnimID]->addTime(deltaTime * topAnimSpeed);

		// apply smooth transitioning between our animations
		fadeAnimations(deltaTime);
	}

	void SinbadCharacter::fadeAnimations(Ogre::Real deltaTime)
	{
		for (int i = 0; i < NUM_ANIMS; i++){
			if (fadingIn[i]){
				// slowly fade this animation in until it has full weight
				Ogre::Real newWeight = anims[i]->getWeight() + deltaTime * ANIM_FADE_SPEED;
				anims[i]->setWeight(Ogre::Math::Clamp<Ogre::Real>(newWeight, 0, 1));
				if (newWeight >= 1) fadingIn[i] = false;
			}else if (fadingOut[i]){
				// slowly fade this animation out until it has no weight, and then disable it
				Ogre::Real newWeight = anims[i]->getWeight() - deltaTime * ANIM_FADE_SPEED;
				anims[i]->setWeight(Ogre::Math::Clamp<Ogre::Real>(newWeight, 0, 1));
				if (newWeight <= 0){
					anims[i]->setEnabled(false);
					fadingOut[i] = false;
				}
			}
		}
	}

	void SinbadCharacter::updateCamera(Ogre::Real deltaTime)
	{
		// place the camera pivot roughly at the character's shoulder
		cameraPivot->setPosition(bodyNode->getPosition() + Ogre::Vector3::UNIT_Y * CAM_HEIGHT);
		// move the camera smoothly to the goal
		Ogre::Vector3 goalOffset = cameraGoal->_getDerivedPosition() - cameraNode->getPosition();
		cameraNode->translate(goalOffset * deltaTime * 9.0f);
		// always look at the pivot
		cameraNode->lookAt(cameraPivot->_getDerivedPosition(), Ogre::Node::TS_WORLD);
	}

	void SinbadCharacter::setPostion(Ogre::Real x,Ogre::Real y,Ogre::Real z)
	{
		bodyNode->setPosition(x,y,z);
	}

	SinbadCharacter::~SinbadCharacter(void)
	{
	}

	void SinbadCharacter::injectMouseMove(const OIS::MouseEvent& evt)
	{
		// update camera goal based on mouse movement
		updateCameraGoal(-0.05f * evt.state.X.rel, -0.05f * evt.state.Y.rel, -0.0005f * evt.state.Z.rel);
	}

	void SinbadCharacter::updateCameraGoal(Ogre::Real deltaYaw, Ogre::Real deltaPitch, Ogre::Real deltaZoom)
	{
		cameraPivot->yaw(Ogre::Degree(deltaYaw), Ogre::Node::TS_WORLD);

		// bound the pitch
		if (!(pivotPitch + deltaPitch > 25 && deltaPitch > 0) &&
			!(pivotPitch + deltaPitch < -60 && deltaPitch < 0)){
				cameraPivot->pitch(Ogre::Degree(deltaPitch), Ogre::Node::TS_LOCAL);
			pivotPitch += deltaPitch;
		}
		
		Ogre::Real dist = cameraGoal->_getDerivedPosition().distance(cameraPivot->_getDerivedPosition());
		Ogre::Real distChange = deltaZoom * dist;

		// bound the zoom
		if (!(dist + distChange < 8 && distChange < 0) &&
			!(dist + distChange > 25 && distChange > 0)){
				cameraGoal->translate(0, 0, distChange, Ogre::Node::TS_LOCAL);
		}
	}

	void SinbadCharacter::injectKeyDown(const OIS::KeyEvent& evt)
	{
		if (evt.key == OIS::KC_Q && (topAnimID == ANIM_IDLE_TOP || topAnimID == ANIM_RUN_TOP)){
			// take swords out (or put them back, since it's the same animation but reversed)
			setTopAnimation(ANIM_DRAW_SWORDS, true);
			timer = 0;
		}else if (evt.key == OIS::KC_E && !swordsDrawn){
			if (topAnimID == ANIM_IDLE_TOP || topAnimID == ANIM_RUN_TOP){
				// start dancing
				setBaseAnimation(ANIM_DANCE, true);
				setTopAnimation(ANIM_NONE);
				// disable hand animation because the dance controls hands
				anims[ANIM_HANDS_RELAXED]->setEnabled(false);
			}else if (baseAnimID == ANIM_DANCE){
				// stop dancing
				setBaseAnimation(ANIM_IDLE_BASE);
				setTopAnimation(ANIM_IDLE_TOP);
				// re-enable hand animation
				anims[ANIM_HANDS_RELAXED]->setEnabled(true);
			}
		}

		// keep track of the player's intended direction
		else if (evt.key == OIS::KC_W) keyDirection.z = -1;
		else if (evt.key == OIS::KC_A) keyDirection.x = -1;
		else if (evt.key == OIS::KC_S) keyDirection.z = 1;
		else if (evt.key == OIS::KC_D) keyDirection.x = 1;

		else if (evt.key == OIS::KC_SPACE && (topAnimID == ANIM_IDLE_TOP || topAnimID == ANIM_RUN_TOP)){
			// jump if on ground
			setBaseAnimation(ANIM_JUMP_START, true);
			setTopAnimation(ANIM_NONE);
			timer = 0;
		}

		if (!keyDirection.isZeroLength() && baseAnimID == ANIM_IDLE_BASE){
			// start running if not already moving and the player wants to move
			setBaseAnimation(ANIM_RUN_BASE, true);
			if (topAnimID == ANIM_IDLE_TOP) setTopAnimation(ANIM_RUN_TOP, true);
		}
	}

	void SinbadCharacter::injectKeyUp(const OIS::KeyEvent& evt)
	{
		// keep track of the player's intended direction
		if (evt.key == OIS::KC_W && keyDirection.z == -1) keyDirection.z = 0;
		else if (evt.key == OIS::KC_A && keyDirection.x == -1) keyDirection.x = 0;
		else if (evt.key == OIS::KC_S && keyDirection.z == 1) keyDirection.z = 0;
		else if (evt.key == OIS::KC_D && keyDirection.x == 1) keyDirection.x = 0;

		if (keyDirection.isZeroLength() && baseAnimID == ANIM_RUN_BASE){
			// stop running if already moving and the player doesn't want to move
			setBaseAnimation(ANIM_IDLE_BASE);
			if (topAnimID == ANIM_RUN_TOP) setTopAnimation(ANIM_IDLE_TOP);
		}
	}

	void SinbadCharacter::injectMouseDown(const OIS::MouseEvent& evt, OIS::MouseButtonID id)
	{
		if (swordsDrawn && (topAnimID == ANIM_IDLE_TOP || topAnimID == ANIM_RUN_TOP)){
			// if swords are out, and character's not doing something weird, then SLICE!
			if (id == OIS::MB_Left) setTopAnimation(ANIM_SLICE_VERTICAL, true);
			else if (id == OIS::MB_Right) setTopAnimation(ANIM_SLICE_HORIZONTAL, true);
			timer = 0;
		}
	}

}
